Keep testing your iron.io Ruby Workers

2012-07-23

One of the best practices in software development is making sure your software actually keeps working.

If you are using IronWorker to parallelize processing, it's good to know that your code actually works before scheduling it. The most common way to do so is Test::Unit, which conveniently comes in the Ruby standard library. Of course you may use any other framework, be it RSpec, bacon, or even kintama.

One of the things our users sometimes ask for is how they can avoid using a separate file just to use the executable as a library, so their worker layout would look something like this:

.
├── foo_lib.rb
├── foo.rb
├── foo.worker
└── test_foo.rb

Instead they prefer something more like:

.
├── foo.rb
├── foo.worker
└── test_foo.rb

So here is just a quick example of how to use $0 and __FILE__ in Ruby to conditionally use a file as a library or as an executable. It's a pattern at least as old as Ruby, but still quite useful.

First we start with our actual worker, which does the heavy lifting, trying to increase entropy in the universe. Little does it know that rand is only pseudo-random. At the end of the file, after the whole EntropyGatherer class has been executed, we put our if $0 == __FILE__, and place whatever code is needed to initialize and run the class. Of course, you could just have a single method instead of a class, or use a module with module_function, but this will have to do for our purposes.

I call this file entropy_gatherer.rb

class EntropyGatherer
  attr_reader :results

  def initialize
    @results = []
  end

  def run
    1.upto 100 do
      @results << rand
    end
  end
end

if $0 == __FILE__
  gatherer = EntropyGatherer.new
  gatherer.run
  puts gatherer.results
end

And here we have a file called test_entropy_gatherer.rb, which uses the familiar Test::Unit and can be executed with testrb test_entropy_gaterer.rb.

require "test/unit"
require_relative 'entropy_gatherer'

class TestEntropyGatherer < Test::Unit::TestCase
  # this runs before all tests
  def setup
    @gatherer = EntropyGatherer.new
    @gatherer.run
  end

  def test_result_size
    assert(@gatherer.results.size == 100, "wrong number of results")
  end

  def test_result_range
    @gatherer.results.each do |result|
      assert(result <= 1.0, "result greater than 1.0")
      assert(result >= 0.0, "result smaller than 0.0")
    end
  end
end

And here's how that all plays together.

iota ~/tmp/iworker_rand % testrb -v test_entropy_gatherer.rb
Run options: -v

# Running tests:

TestEntropyGatherer#test_result_range = 0.00 s = .
TestEntropyGatherer#test_result_size = 0.00 s = .


Finished tests in 0.000814s, 2455.7896 tests/s, 246806.8595 assertions/s.

2 tests, 201 assertions, 0 failures, 0 errors, 0 skips